home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / Libraries / VideoToolbox 94.11.17 / VideoToolboxSources / VBLInstall.c < prev    next >
Encoding:
Text File  |  1994-11-17  |  16.2 KB  |  391 lines  |  [TEXT/KAHL]

  1. /*
  2. VBLInstall.c
  3.  
  4. This is the Apple-recommended way of synchronizing your program to a video
  5. display. In the Macintosh, interrupt service routines run at a high processor
  6. priority, i.e. they block interrupts while they run, so Apple advises that they
  7. be very quick, to avoid missing interrupts. That is the approach taken here,
  8. using the interrupt service routines solely to bump a frame counter and set a
  9. newFrame flag to true. Typically your main program will iteratively test the
  10. newFrame flag and, when it's true, clear it and do some action that you want to
  11. do once per frame, in synch with the video frames.
  12.  
  13. OSErr VBLInstall(VBLTaskAndA5 *vblData,GDHandle device,int frames);
  14.  
  15. The first argument is a pointer to a user-supplied data structure. "device" is a
  16. handle to the video screen that you want to synchronize to, or NULL if you want
  17. to synchronize to the System VBL rate (usually 60.15 Hz). The last argument,
  18. "frames", specifies how many times you want the interrupt to occur before the
  19. routine disables itself. If frames<0 then the interrupt will recur indefinitely.
  20.  
  21. VBLInstall zeroes vblData->frame, and sets vblData->framesDesired and vblData->framesLeft equal to
  22. "frames". While vblData->framesLeft!=0, at each end-of-frame interrupt
  23. vblData->frame will be incremented and vblData->newFrame will be set to 1. 
  24. vblData->framesLeft will be decremented if
  25. it's >0, but left alone if it's negative. If vblData->framesLeft is decremented
  26. to zero then the interrupt service routine is done, and won't reenable the
  27. interrupt.
  28.  
  29. Here's a minimal program, extracted from NoiseVBL.c, that uses these routines to
  30. show a 100-frame movie, with a new image on each video frame:
  31.  
  32.     VBLTaskAndA5 noiseVBL;
  33.     GDHandle device=GetMainDevice();
  34.     int frames=100,error;
  35.  
  36.     noiseVBL.subroutine=NULL;                // request default subroutine
  37.     error=VBLInstall(&noiseVBL,device,frames);
  38.     if(error)PrintfExit("VBLInstall error %d\n",error);
  39.     noiseVBL.vbl.vblCount=1;    // enable the interrupt service routine
  40.     while(noiseVBL.framesLeft){
  41.         if(noiseVBL.newFrame){
  42.             noiseVBL.newFrame=0;
  43.             CopyBitsQuickly((BitMap *)&(movie[noiseVBL.frame])
  44.                 ,(BitMap *)*((CGrafPtr)window)->portPixMap
  45.                 ,&noiseImage[0].bounds,&window->portRect,srcCopy,NULL);
  46.         }
  47.     }
  48.     VBLRemove(&noiseVBL);
  49.  
  50. Apple warns that interrupt service routines and any data that they access must
  51. be locked into memory, not swapped out, e.g. due to operation of the Memory
  52. Manager or Virtual Memory. It is dangerous to allow your compiler to install
  53. debugging code into interrupt service routines. It is VERY important that you
  54. call VBLRemove() before quitting. Once installed, slot-based interrupt tasks
  55. keep going forever. (Turning off vblData->vbl.vblCount disables the routine, but
  56. doesn't remove it.) The tasks are not removed by the Finder or System when your
  57. application finishes, even though the interrupt service routine's code and data
  58. will probably be overwritten.
  59.  
  60. Actually, things aren't that bad, because I've added a call to _atexit()
  61. requesting that all the VBL tasks installed by VBLInstall() be removed whenever
  62. the program terminates, whether normally or abnormally. This prevents the
  63. otherwise very annoying crash that would accompany premature termination, e.g.
  64. by typing command-., while the VBL task is active.
  65.  
  66. Writing an interrupt service routine, to be called once per video frame, is
  67. slightly tricky. VBLInterruptServiceRoutine does all the dirty work, and then
  68. calls a user-supplied subroutine that does whatever you want. If all you want to
  69. do is advance a frame counter until you reach the desired number of frames then
  70. you may wish to use the default FrameSubroutine() instead of writing your own.
  71. Put the address of whatever routine you want to use into the "subroutine"
  72. element of the VBLTaskAndA5 structure, or, to request use of FrameSubroutine,
  73. supply the address NULL. However, if your compiler uses Universal Headers, then you 
  74. must supply a Universal Procedure Pointer instead of the subroutine address:
  75.  
  76. #if USESROUTINEDESCRIPTORS
  77.     noiseVBL.subroutine=NewVBLProc(myRoutine);
  78. #else
  79.     noiseVBL.subroutine=myRoutine;
  80. #endif
  81.  
  82. FrameSubroutine() uses Timer.c. If this program runs on an old System (pre
  83. 6.05?) lacking the Revised Time Manager, which Timer.c requires, then
  84. SimpleVBLSubroutine() will be used instead. The Timer is used to discard
  85. spurious interrupts that occur within 5 ms of the previous. This is necessary
  86. because some of the Apple video cards generate several interrupts during the
  87. vertical blanking interval, even though Apple's documentation clearly indicates
  88. that they should only generate one. The fix (holding off for 5 ms) was suggested
  89. by Raynald Comtois.
  90.  
  91. The 1992 Apple "Inside Macintosh: Processes" book explains how to code a task
  92. that is to be performed each time your video device produces a vertical blanking
  93. level (i.e. between video frames). It's quite tricky. Therefore I wrote a
  94. generic one, that in turn, calls your custom one, after all the tricky bits have
  95. been taken care of.
  96.  
  97. There are some subtleties to the VBL interrupt service routine. The main one is
  98. that all the Macintosh compilers reference global variables relative to register
  99. A5, but register A5 may have the wrong value (corresponding to a different
  100. application) at interrupt time. So we save the right value of A5 inside our
  101. structure and restore A5 before calling our Task().
  102.  
  103. A further subtlety is that the new generation of optimizing compilers might
  104. notice that A5 is being modified, so it would be dangerous to access globals at
  105. all in InterruptServiceRoutine(), even though it's safe to do so in Task(). In
  106. fact Task() doesn't use any globals, but it could.
  107.  
  108. The fact that the address of our structure is in register A0 when the interrupt
  109. service routine is called results from the fact that the low level hardware VBL
  110. interrupt is intercepted by the operating system, which then calls our routine.
  111.  
  112. The VBLTaskAndA5 struct extends Apple's VBLTask struct by adding some useful
  113. information at the end. You may choose to copy this, or define your own, adding
  114. more stuff at the end. However, you may not want to bother, considering that
  115. your interrupt service routine can freely access global variables.
  116. Alternatively, I added a generic pointer at the end, which you may use to pass
  117. the address of any stuff that you want to access within your subroutine.
  118.  
  119. Macintosh Technical Note 180 ("Multifinder Miscellanea"), p. 5 says:
  120. "GetVBLRec() returns the address of the VBLRec associated with our VBL task.
  121. This works because on entry into the VBL task, A0 points to the theVBLTask field
  122. in the VBLRec record, which is the first field in the record and that is the
  123. address we return. Note that this method works whether the VBLRec is allocated
  124. globally, in the heap...or...on the stack. ... This trick allows us to get to
  125. the saved A5, but it could also be used to get to anything we wanted to store in
  126. the record." (quoted by Jamie McCarthy in comp.sys.mac.prog 10/15/92.)
  127.  
  128. HISTORY:
  129. 8/22/92 dgp wrote it, based on code extracted from my NoiseVBL.c
  130. 8/26/92    dgp    added VBLRemoveAll(), which is automatically placed in the _atexit()
  131.             queue, so it all your VBL tasks will be removed from the queue when
  132.             your program exits, whether normal or abnormally.
  133. 9/9/92    dgp    fixed erroneous attempt to use slot zero when NULL device was passed.
  134. 9/10/92    dgp    added calls to VM to HoldMemory() and UnHoldMemory(). According to Apple's
  135.             Memory book this isn't strictly necessary, since VBL tasks will 
  136.             be called only when it's safe.
  137. 9/17/92    dgp    Transferred FrameSubroutine() here from GDFrameRate.c. Now use 
  138.             FrameSubroutine() instead of SimpleVBLSubroutine() as the default,
  139.             provided we can use Timer.c, otherwise fall back to using 
  140.             SimpleVBLSubroutine.
  141. 10/9/92    dgp    Automatically set up and dispose of the timer used by FrameSubroutine.
  142.             Added a field to the VBLTaskAndA5 structure to hold the timer pointer,
  143.             instead of using up the generic ptr.
  144.             WARNING: old programs (written during 9/92) that explicitly request use of 
  145.             FrameSubroutine must be changed, because at that time it was the user's 
  146.             responsibility to set up and dispose of the timer, whereas it's now done 
  147.             for you. To remind you to make this change "FrameSubroutine" is no longer 
  148.             in VideoToolbox.h, it's treated as a private routine here. To obtain the 
  149.             services of FrameSubroutine, just specify a NULL in the VBLTaskAndA5 
  150.             subroutine field.
  151. 11/17/92 dgp In VBLRemove(), first disable the interrupt, then clean up.
  152.             Fixed error that could cause bus error in VBLRemoveAll.
  153.             VBLInstall() now installs VBLRemoveAll() only once in the _atexit()
  154.             queue, no matter how many times you call it.
  155. 11/24/92 dgp Minor updating of comments.
  156. 7/9/93    dgp    Test MATLAB in if() instead of #if. 
  157. 2/28/94    dgp    In response to query by Mike Tarr (tarr-michael@CS.YALE.EDU), changed 
  158.             frames argument from int to long, and now allow frames==-1 to request 
  159.             that the interrupt service routine continue working indefinitely.
  160. 3/5/94    dgp    In response to a bug report by Mike Tarr, finished the 2/28/94 change,
  161.             which I'd foolishly only done to SimpleVBLSubroutine and not to 
  162.             FrameSubroutine.
  163. 5/28/94    dgp    Made compatible with Apple's Universal Headers. I also attempted to
  164.             make the code PowerPC compatible, but that remains to be tested.
  165. 10/1/94    dgp    Added new "frame" field to VBLTaskAndA5 struct, which counts up from zero.
  166. 10/24/94 dgp Made declaration of GetA0() explicitly indicate that answer is returned in D0, for
  167. better compatibility with Metrowerks CodeWarrior C.
  168. 10/27/94 dgp It is very annoying for the machine to crash anytime you try to quit your application
  169. by escaping via MacsBugs escape to shell. The problem is that CodeWarrior 4.5 doesn't attach
  170. the atexit() tasks to _EscapeToShell. So I do it here. 
  171. */
  172. #include "VideoToolbox.h"
  173. #include <Traps.h>
  174. //#include <Retrace.h>
  175. void VBLRemoveAll(void);
  176. static void PatchExitToShell(void);
  177. #if !defined(NewVBLProc)
  178.     typedef ProcPtr UniversalProcPtr;
  179.     #define NewVBLProc(userRoutine) (UniversalProcPtr)(userRoutine)
  180.     #define DisposeRoutineDescriptor(userRoutine) ()
  181. #endif
  182. #if __powerc
  183.     void VBLInterruptServiceRoutine(register VBLTaskAndA5 *vblData);
  184. #else
  185.     void VBLInterruptServiceRoutine(void);
  186. #endif
  187. static void FrameSubroutine(VBLTaskAndA5 *vblData);
  188.  
  189. /* This is just for reference. The original is in VideoToolbox.h
  190. struct VBLTaskAndA5 {
  191.     volatile VBLTask vbl;
  192.     long ourA5;
  193.     #if USESROUTINEDESCRIPTORS
  194.         UniversalProcPtr subroutine;
  195.     #else
  196.         void (*subroutine)(struct VBLTaskAndA5 *vblData);
  197.     #endif
  198.     GDHandle device;
  199.     long slot;
  200.     volatile long newFrame;                // Boolean
  201.     volatile long frame;                // count up from zero
  202.     volatile long framesLeft;            // count down to zero
  203.     long framesDesired;
  204.     Timer *frameTimer;                    // time ms since last VBL interrupt, see Timer.c
  205.     void *ptr;                            // use this for whatever you want
  206. };
  207. typedef struct VBLTaskAndA5 VBLTaskAndA5;
  208. */
  209. #define TASK_SIZE 1000    // Generous guess for size of routine
  210. static long vmPresent=0;
  211. static UniversalProcPtr vblProcPtr,frameSubroutineProcPtr,simpleVBLSubroutineProcPtr;
  212.  
  213. OSErr VBLInstall(VBLTaskAndA5 *vblData,GDHandle device,long frames)
  214. // trivial, but verbose
  215. {
  216.     static int firstTime=1,slotRoutinesAvailable;
  217.     static long timeManagerVersion=0;
  218.     
  219.     if(firstTime){
  220.         firstTime=0;
  221.         slotRoutinesAvailable=TrapAvailable(_SlotVInstall);
  222.         Gestalt(gestaltVMAttr,&vmPresent);
  223.         vmPresent &= gestaltVMPresent;
  224.         if(!MATLAB){
  225.             #if __MWERKS__
  226.                 // In CW4.5 _atexit() only gets called if you quit via abort() or exit().
  227.                 PatchExitToShell();
  228.             #else
  229.                 _atexit(VBLRemoveAll);
  230.             #endif
  231.         }
  232.         Gestalt(gestaltTimeMgrVersion,&timeManagerVersion);
  233.         vblProcPtr=NewVBLProc(VBLInterruptServiceRoutine);
  234.         frameSubroutineProcPtr=NewVBLProc(FrameSubroutine);
  235.         simpleVBLSubroutineProcPtr=NewVBLProc(SimpleVBLSubroutine);
  236.     }
  237.     vblData->device=device;
  238.     if(device!=NULL && (*device)->gdRefNum!=0 && slotRoutinesAvailable)
  239.         vblData->slot=GetDeviceSlot(device);
  240.     else vblData->slot=-1;
  241.     vblData->vbl.vblAddr=(void *)vblProcPtr;
  242.     vblData->vbl.qType=vType;
  243.     vblData->vbl.vblCount=0;    /* Initially disable the interrupt service routine */
  244.     vblData->vbl.vblPhase=0;
  245.     vblData->ourA5=SetCurrentA5();
  246.     if(vblData->subroutine==NULL){
  247.         if(timeManagerVersion>=gestaltRevisedTimeMgr){
  248.             vblData->frameTimer=NewTimer();
  249.             StartTimer(vblData->frameTimer);
  250.             vblData->subroutine=(void *)frameSubroutineProcPtr;
  251.         }
  252.         else vblData->subroutine=(void *)simpleVBLSubroutineProcPtr;
  253.     }
  254.     vblData->newFrame=0;
  255.     vblData->frame=0;
  256.     vblData->framesLeft=vblData->framesDesired=frames;
  257.     if(vmPresent){
  258.         HoldMemory(vblData,sizeof(*vblData));
  259.         HoldMemory(vblData->vbl.vblAddr,TASK_SIZE);
  260.         HoldMemory(vblData->subroutine,TASK_SIZE);
  261.     }
  262.     if(vblData->slot>=0)return SlotVInstall((QElemPtr)vblData,vblData->slot);
  263.     else return VInstall((QElemPtr)vblData);
  264. }
  265.  
  266. OSErr VBLRemove(VBLTaskAndA5 *vblData)
  267. {
  268.     int error;
  269.  
  270.     // only remove it if we installed it
  271.     if(vblData->vbl.vblAddr != vblProcPtr)return 0;
  272.     if(vblData->slot>=0)error=SlotVRemove((QElemPtr)vblData,vblData->slot);
  273.     else error=VRemove((QElemPtr)vblData);
  274.     if(vmPresent){
  275.         UnholdMemory(vblData,sizeof(vblData));
  276.         UnholdMemory(vblData->vbl.vblAddr,TASK_SIZE);
  277.         UnholdMemory(vblData->subroutine,TASK_SIZE);
  278.     }
  279.     if(vblData->subroutine==(void *)frameSubroutineProcPtr && vblData->frameTimer!=NULL)
  280.         DisposeTimer(vblData->frameTimer);
  281.     return error;
  282. }
  283.  
  284. void VBLRemoveAll(void)
  285. {
  286.     QHdrPtr qHeader;
  287.     QElemPtr q;
  288.     
  289.     qHeader=GetVBLQHdr();
  290.     q=qHeader->qHead;
  291.     while(q!=NULL){
  292.         VBLRemove((VBLTaskAndA5 *)q);    // only removes ours
  293.         q=q->qLink;
  294.     }
  295. }    
  296.  
  297. #if (THINK_C || THINK_CPLUS)
  298.     #pragma options(!profile)    // it would be dangerous to call the profiler from here
  299.     #pragma options(assign_registers,redundant_loads)
  300. #endif
  301.  
  302. #if __powerc
  303.     void VBLInterruptServiceRoutine(register VBLTaskAndA5 *vblData)
  304.     {
  305.         CallVBLProc(vblData->subroutine,vblData);    // call user's task, pass data ptr
  306.     }
  307. #else
  308.     #pragma parameter __D0 GetA0
  309.     long GetA0(void)=0x2008;        /* MOVE.L A0,D0 */
  310. //    Ptr GetA0(void):__D0 =0x2008;    /* MOVE.L A0,D0 */
  311.  
  312.     void VBLInterruptServiceRoutine(void)
  313.     {
  314.         register long oldA5;
  315.         register VBLTaskAndA5 *vblData;
  316.     
  317.         vblData = (VBLTaskAndA5 *)GetA0();
  318.         oldA5 = SetA5(vblData->ourA5);
  319.         // call user's task, pass data ptr
  320.         #if USESROUTINEDESCRIPTORS
  321. // WARNING: uppVBLProcInfo is the wrong selector.
  322.             CallUniversalProc(vblData->subroutine,uppVBLProcInfo,vblData);
  323.         #else
  324.             (*vblData->subroutine)(vblData);
  325.         #endif
  326.         SetA5(oldA5);
  327.     }
  328. #endif
  329.  
  330. void SimpleVBLSubroutine(VBLTaskAndA5 *vblData)
  331. {
  332.     vblData->newFrame=1;
  333.     vblData->frame++;
  334.     if(vblData->framesLeft>0)vblData->framesLeft--;
  335.     if(vblData->framesLeft!=0)vblData->vbl.vblCount=1;
  336. }
  337.  
  338. static void FrameSubroutine(VBLTaskAndA5 *vblData)
  339. {
  340.     // The 1991-2 Apple video cards emit several vbl interrupts per frame,
  341.     // which violates Apple's documentation.
  342.     // So we use the Time Manager to ignore any interrupt that occurs within 5 ms
  343.     // of the most recent interrupt, for that video device.
  344.     // Thus this frame counter should work correctly on all video cards.
  345.     // Suggested by Raynald Comtois.
  346.     if(StopTimer(vblData->frameTimer)>5000){
  347.         vblData->newFrame=1;                                    // set new-frame flag
  348.         vblData->frame++;
  349.         if(vblData->framesLeft>0)vblData->framesLeft--;
  350.         if(vblData->framesLeft!=0)vblData->vbl.vblCount=1;        // re-enable interrupt
  351.     }else vblData->vbl.vblCount=1;                                // re-enable interrupt
  352.     StartTimer(vblData->frameTimer);
  353. }
  354.  
  355. /*
  356. The following code, which seems to work on 68k and ppc,
  357. is based on examples that appeared in UseNet csmp-digest-v3-046
  358. by Kevin Bell (kbell@cs.utexas.edu) and Bill Hofmann (wdh@netcom.com)
  359. in response to a query by Steve Coy (stevec@jolt.mpx.com.au)
  360. */
  361.  
  362. #if UNIVERSAL_HEADERS
  363.     typedef UniversalProcPtr TrapAddressType;
  364. #else
  365.     #define UniversalProcPtr ProcPtr
  366.     #define NewRoutineDescriptor(a,b,c) a
  367.     typedef long TrapAddressType;
  368. #endif
  369. static UniversalProcPtr oldExitToShellTrapAddress=NULL;
  370. static void PatchExitToShell(void);
  371. static pascal void MyExitToShell(void);
  372. #include <Traps.h>    // _ExitToShell
  373.  
  374. static void PatchExitToShell(void)
  375. {
  376.     UniversalProcPtr myExitToShellUPP;
  377.     if(oldExitToShellTrapAddress==NULL){
  378.         myExitToShellUPP=NewRoutineDescriptor((ProcPtr)MyExitToShell,kPascalStackBased,GetCurrentISA());
  379.         oldExitToShellTrapAddress=(UniversalProcPtr)GetToolTrapAddress(_ExitToShell);
  380.         SetToolTrapAddress((TrapAddressType)myExitToShellUPP,_ExitToShell);
  381.     }
  382. }
  383.  
  384. static pascal void MyExitToShell(void)
  385. {
  386.     SetCurrentA5();
  387.     SetToolTrapAddress((TrapAddressType)oldExitToShellTrapAddress,_ExitToShell);
  388.     VBLRemoveAll();
  389.     ExitToShell();
  390. }
  391.